//----------------------------------------------------------------------------
// util.cpp
//
// utility functions for nclip
//
// Copyright (C) 2011 Neil Butterworth
//----------------------------------------------------------------------------

#include <windows.h>
#include <string>
#include <cstdlib>

#include "error.h"
#include "util.h"

using namespace std;

//----------------------------------------------------------------------------
// Convert string to long, returning success indicator
//----------------------------------------------------------------------------

bool ToInteger( const string & s, long & n ) {
	char * p;
	n = strtol( s.c_str(), & p, 10 );	// base 10 conversion
	if ( * p != 0 ||  n == LONG_MAX || n == LONG_MIN ) {
		return false;
	}
	else {
		return true;
	}
}

//----------------------------------------------------------------------------
// Get last Windows error as string - code copied from MSDN examples.
//----------------------------------------------------------------------------

string LastWinError() {
	char * lpMsgBuf;
	::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
					FORMAT_MESSAGE_FROM_SYSTEM |
					FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					::GetLastError(),
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					(LPTSTR) &lpMsgBuf,
					0,
					NULL );
	string msg ( lpMsgBuf );
	::LocalFree( lpMsgBuf );
	return  msg;
}


//----------------------------------------------------------------------------
// Mappings to and from integer version of clipboard format and a string
// representation. These are the "standard" formats according to MS docs.
//----------------------------------------------------------------------------

struct FormatMapEntry {
	UINT val;
	const char * const str;
};

FormatMapEntry FormatMap[] = {
	{ CF_BITMAP,"CF_BITMAP" },
	{ CF_DIB ,"CF_DIB" },
	{ CF_DIF,"CF_DIF" },
	{ CF_DSPBITMAP,"CF_DSPBITMAP" },
	{ CF_DSPENHMETAFILE,"CF_DSPENHMETAFILE" },
	{ CF_DSPMETAFILEPICT,"CF_DSPMETAFILEPICT" },
	{ CF_DSPTEXT,"CF_DSPTEXT" },
	{ CF_ENHMETAFILE ,"CF_ENHMETAFILE" },
	{ CF_HDROP,"CF_HDROP" },
	{ CF_LOCALE,"CF_LOCALE" },
	{ CF_METAFILEPICT ,"CF_METAFILEPICT" },
	{ CF_OEMTEXT,"CF_OEMTEXT" },
	{ CF_OWNERDISPLAY,"CF_OWNERDISPLAY" },
	{ CF_PALETTE,"CF_PALETTE" },
	{ CF_PENDATA,"CF_PENDATA" },
	{ CF_RIFF,"CF_RIFF" },
	{ CF_SYLK,"CF_SYLK" },
	{ CF_TEXT,"CF_TEXT" },
	{ CF_TIFF,"CF_TIFF" },
	{ CF_UNICODETEXT,"CF_UNICODETEXT" },
	{ CF_WAVE,"CF_WAVE" },
	{ 0, 0 }
};

//----------------------------------------------------------------------------
// Convert integer CF_ value to string representation. If there is no
// available standard name, look up custom name.
//----------------------------------------------------------------------------

string CFToStr( UINT cf ) {
	unsigned int i = 0;
	while( FormatMap[i].str ) {
		if ( FormatMap[i].val == cf ) {
			return FormatMap[i].str;
		}
		i++;
	}
	const unsigned int BUFSIZE = 256;
	char buffer[BUFSIZE];
	string name;
	if ( GetClipboardFormatName( cf, buffer, BUFSIZE ) ) {
		name = buffer;
	}
	else {
		name = Str( cf );
	}
	return "Unknown or custom format: " + name;
}

//----------------------------------------------------------------------------
// Convert a CF_ style string to the matching integer value
//----------------------------------------------------------------------------

UINT StrToCF( const string & s ) {
	unsigned int i = 0;
	while( FormatMap[i].str ) {
		if ( stricmp( FormatMap[i].str, s.c_str() ) == 0 ) {
			return FormatMap[i].val;
		}
		i++;
	}
	throw( ClipError( "Unknown format: " + s ) );
}


//----------------------------------------------------------------------------
// Is this a format we know about?
//----------------------------------------------------------------------------

bool KnownFormat( UINT cf ) {
	unsigned int i = 0;
	while( FormatMap[i].str ) {
		if ( FormatMap[i].val == cf ) {
			return true;
		}
		i++;
	}
	return false;
}

//----------------------------------------------------------------------------
// Is this format presently in the clipboard?
//----------------------------------------------------------------------------

bool HasFormat( UINT cf ) {
	UINT f = 0;
	while( (f = ::EnumClipboardFormats( f )) != 0  ) {
		if ( f == cf ) {
			return true;
		}
	}
	return false;
}

//----------------------------------------------------------------------------
// Check if format matches string. Format may be specified in any case, and
// the leading CF_ may be omitted.
//----------------------------------------------------------------------------

bool HasFormat( const string &  cf ) {
	UINT f = 0;
	while( (f = ::EnumClipboardFormats( f )) != 0 ) {
		const int BUFSIZE = 100;
		char name[BUFSIZE];
		if ( ::GetClipboardFormatName( f, name, BUFSIZE ) ) {
			if ( stricmp( name, cf.c_str() ) == 0 ) {
				return true;
			}
		}
		string efmt =  CFToStr( f );
		if ( stricmp( efmt.c_str(), cf.c_str() ) == 0  ) {
			return true;
		}
		if ( stricmp( efmt.substr( 3 ).c_str(), cf.c_str() ) == 0 ) {
			return true;
		}
	}
	return false;
}

//----------------------------------------------------------------------------
// Check clipboard has text available, or report error
//----------------------------------------------------------------------------

void MustHaveText() {

	UINT f = 0;

	while( 1 ) {
		f = ::EnumClipboardFormats( f );
		if ( f == CF_TEXT ) {
			return;
		}
		else if ( f == 0 ) {
			if ( ::GetLastError() == NO_ERROR ) {
				throw ClipError( "No text in clipboard" );
			}
			else {
				throw ClipError( LastWinError() );
			}
		}
		else {
			continue;
		}
	}
}

//----------------------------------------------------------------------------
// Get textual clipboard data
//----------------------------------------------------------------------------

string GetTextData( void ) {
	MustHaveText();
	HANDLE hdata = ::GetClipboardData( CF_TEXT );
	if ( hdata == NULL ) {
		::CloseClipboard();
		throw ClipError( LastWinError() );
	}
	char * p = (char *) ::GlobalLock( hdata );
	string data( p );
	::GlobalUnlock( hdata );
	return data;
}

//----------------------------------------------------------------------------
// Put specifed string in the clipboard as text. If we are in append mode,
// append the new text to any existing stuff.
//----------------------------------------------------------------------------

void SetTextData( const std::string & s, bool append ) {

	string old;
	if ( append && HasFormat( CF_TEXT ) ) {
		old = GetTextData();
	}

	HANDLE h = ::GlobalAlloc(GHND | GMEM_DDESHARE, old.size() + s.size() + 1 );
	if ( h == 0 ) {
		throw ClipError( "Out of memory" );
	}
	char * p = (char *) ::GlobalLock( h );
	strcpy( p, old.c_str() );
	strcat( p, s.data() );
	::GlobalUnlock( h );
	::EmptyClipboard();
	::SetClipboardData( CF_TEXT, h );
}

//----------------------------------------------------------------------------
// Get file list from CF_HDROP data.
//----------------------------------------------------------------------------

vector<string> GetDropFiles() {
	HDROP  hdata = (HDROP) ::GetClipboardData( CF_HDROP );
	if ( hdata == NULL ) {
		::CloseClipboard();
		throw ClipError( LastWinError() );
	}
	vector <string> out;
	const unsigned int BSIZE = 2000;
	vector <char> buffer( BSIZE );
	unsigned int n = DragQueryFile( hdata, 0xFFFFFFFF, &buffer[0], BSIZE );
	for ( unsigned int i = 0; i < n; i++ ) {
		DragQueryFile( hdata, i, &buffer[0], BSIZE );
		out.push_back( &buffer[0] );
	}
	return out;
}






